/*
 * Copyright (C) 2015 Quinn Chen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.leaking.slideswitch;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

/**
 * 滑动开关
 */
public class SlideSwitch extends Component implements Component.DrawTask, Component.TouchEventListener,
        Component.EstimateSizeListener {
    /**
     * 方形角
     */
    public static final int SHAPE_RECT = 1;

    /**
     * 圆形角
     */
    public static final int SHAPE_CIRCLE = 2;

    private static final String THEME_COLOR = "theme_color";
    private static final String IS_OPEN = "is_open";
    private static final String SHAPE = "shape";
    private static final int RIM_SIZE = 6;
    private static final int DEFAULT_COLOR_THEME = Color.getIntColor("#ff00ee00");

    // 3 attributes
    private int color_theme = DEFAULT_COLOR_THEME;
    private boolean isOpen;
    private int shape;
    // varials of drawing
    private Paint paint;
    private RectFloat backRect;
    private RectFloat frontRect;
    private RectFloat frontCircleRect;
    private RectFloat backCircleRect;
    private float alpha;
    private int max_left;
    private int min_left;
    private int frontRect_left;
    private int frontRect_left_begin = RIM_SIZE;
    private int eventStartX;
    private int eventLastX;
    private int diffX = 0;
    private boolean slideable = true;
    private SlideListener listener;

    public SlideSwitch(Context context) {
        this(context, null);
    }

    public SlideSwitch(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public SlideSwitch(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    public SlideSwitch(Context context, AttrSet attrSet, int resId) {
        super(context, attrSet, resId);
        init(attrSet);
    }


    /**
     * 监听开关事件
     */
    public interface SlideListener {
        void open(SlideSwitch slideSwitch);

        void close(SlideSwitch slideSwitch);
    }

    private void init(AttrSet attrs) {
        listener = null;
        paint = new Paint();
        paint.setAntiAlias(true);
        if (attrs != null) {
            boolean hasAttr;
            hasAttr = attrs.getAttr(THEME_COLOR).isPresent();
            if (hasAttr) {
                color_theme = attrs.getAttr(THEME_COLOR).get().getIntegerValue();
            }
            hasAttr = attrs.getAttr(IS_OPEN).isPresent();
            if (hasAttr) {
                isOpen = attrs.getAttr(IS_OPEN).get().getBoolValue();
            }
            hasAttr = attrs.getAttr(SHAPE).isPresent();
            if (hasAttr) {
                shape = attrs.getAttr(SHAPE).get().getIntegerValue();
            }
        }
        addDrawTask(this);
        setTouchEventListener(this);
        setEstimateSizeListener(this);
    }


    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int width = measureDimension(280, widthMeasureSpec);
        int height = measureDimension(140, heightMeasureSpec);
        if (shape == SHAPE_CIRCLE) {
            if (width < height) {
                width = height * 2;
            }
        }
        setEstimatedSize(EstimateSpec.getSizeWithMode(width, EstimateSpec.PRECISE),
                EstimateSpec.getSizeWithMode(height, EstimateSpec.PRECISE));
        initDrawingVal(width, height);
        return true;
    }


    private int measureDimension(int defaultSize, int measureSpec) {
        int result = 0;
        int specMode = EstimateSpec.getMode(measureSpec);
        int specSize = EstimateSpec.getSize(measureSpec);
        if (specMode == EstimateSpec.PRECISE) {
            result = specSize;
        } else {
            result = defaultSize; // UNSPECIFIED
            if (specMode == EstimateSpec.NOT_EXCEED) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }


    private void initDrawingVal(int width, int height) {
        backCircleRect = new RectFloat();
        frontCircleRect = new RectFloat();
        frontRect = new RectFloat();
        backRect = new RectFloat(0, 0, width, height);
        min_left = RIM_SIZE;
        if (shape == SHAPE_RECT) {
            max_left = width / 2;
        } else {
            max_left = width - (height - 2 * RIM_SIZE) - RIM_SIZE;
        }
        if (isOpen) {
            frontRect_left = max_left;
            alpha = 1;
        } else {
            frontRect_left = RIM_SIZE;
            alpha = 0;
        }
        frontRect_left_begin = frontRect_left;
    }


    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (shape == SHAPE_RECT) {
            paint.setColor(Color.GRAY);
            canvas.drawRect(backRect, paint);
            paint.setColor(new Color(color_theme));
            paint.setAlpha(alpha);
            canvas.drawRect(backRect, paint);
            frontRect.left = frontRect_left;
            frontRect.top = RIM_SIZE;
            frontRect.right = frontRect_left + getWidth() / 2 - RIM_SIZE;
            frontRect.bottom = getHeight() - RIM_SIZE;
            paint.setColor(Color.WHITE);
            canvas.drawRect(frontRect, paint);
        } else {
            // draw circle
            int radius;
            radius = (int) (backRect.getHeight() / 2);
            paint.setColor(Color.GRAY);
            backCircleRect.modify(backRect);
            canvas.drawRoundRect(backCircleRect, radius, radius, paint);
            paint.setColor(new Color(color_theme));
            paint.setAlpha(alpha);
            canvas.drawRoundRect(backCircleRect, radius, radius, paint);
            frontRect.left = frontRect_left;
            frontRect.top = RIM_SIZE;
            frontRect.right = frontRect_left + backRect.getHeight() - 2 * RIM_SIZE;
            frontRect.bottom = backRect.getHeight() - RIM_SIZE;
            frontCircleRect.modify(frontRect);
            paint.setColor(Color.WHITE);
            canvas.drawRoundRect(frontCircleRect, radius, radius, paint);
        }
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (slideable == false) {
            return false;
        }
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                eventStartX = (int) getRawX(touchEvent);
                break;
            case TouchEvent.POINT_MOVE:
                eventLastX = (int) getRawX(touchEvent);
                diffX = eventLastX - eventStartX;
                int tempX = diffX + frontRect_left_begin;
                tempX = (tempX > max_left ? max_left : tempX);
                tempX = (tempX < min_left ? min_left : tempX);
                if (tempX >= min_left && tempX <= max_left) {
                    frontRect_left = tempX;
                    alpha = tempX / (float) max_left;
                    invalidateView();
                }
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                int wholeX = (int) (getRawX(touchEvent) - eventStartX);
                frontRect_left_begin = frontRect_left;
                boolean toRight;
                toRight = (frontRect_left_begin > max_left / 2 ? true : false);
                if (Math.abs(wholeX) < 3) {
                    toRight = !toRight;
                } else {
                    if (frontRect_left_begin == min_left || frontRect_left_begin == max_left) {
                        end(toRight);
                        return true;
                    }
                }
                moveToDest(toRight);
                break;
            default:
                break;
        }
        return true;
    }

    private float getRawX(TouchEvent touchEvent) {
        return touchEvent.getPointerScreenPosition(0).getX();
    }


    /**
     * draw again
     */
    private void invalidateView() {
        getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {
            @Override
            public void run() {
                invalidate();
            }
        });
    }

    /**
     * 监听开关滑动事件
     *
     * @param listener 接口
     */
    public void setSlideListener(SlideListener listener) {
        this.listener = listener;
    }

    private void moveToDest(final boolean toRight) {
        AnimatorValue toDestAnim = new AnimatorValue();
        toDestAnim.setDuration(350);
        toDestAnim.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
        toDestAnim.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                if (toRight) {
                    frontRect_left = (int) (frontRect_left_begin + v * (max_left - frontRect_left_begin));
                    alpha = alpha + v * (1 - alpha);
                } else {
                    frontRect_left = (int) (frontRect_left_begin - v * (frontRect_left_begin - min_left));
                    alpha = alpha * (1 - v);
                }
                invalidateView();
            }
        });
        toDestAnim.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
            }

            @Override
            public void onStop(Animator animator) {
            }

            @Override
            public void onCancel(Animator animator) {
            }

            @Override
            public void onEnd(Animator animator) {
                end(toRight);
            }

            @Override
            public void onPause(Animator animator) {
            }

            @Override
            public void onResume(Animator animator) {
            }
        });
        toDestAnim.start();
    }

    private void end(boolean toRight) {
        if (toRight) {
            isOpen = true;
            if (listener != null) {
                listener.open(SlideSwitch.this);
            }
            frontRect_left_begin = max_left;
            alpha = 1;
        } else {
            isOpen = false;
            if (listener != null) {
                listener.close(SlideSwitch.this);
            }
            frontRect_left_begin = min_left;
            alpha = 0;
        }
    }

    /**
     * 设置开关状态
     *
     * @param isOpen 是否开启
     */
    public void setState(boolean isOpen) {
        this.isOpen = isOpen;
        initDrawingVal(getWidth(), getHeight());
        invalidateView();
        if (listener != null) {
            if (isOpen == true) {
                listener.open(this);
            } else {
                listener.close(this);
            }
        }
    }

    /**
     * 设置开关类型
     *
     * @param shapeType 1.方形角  2.圆形角
     */
    public void setShapeType(int shapeType) {
        this.shape = shapeType;
    }

    /**
     * 设置开关是否可以滑动
     *
     * @param slideable 是否可以滑动
     */
    public void setSlideable(boolean slideable) {
        this.slideable = slideable;
    }


}
